﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using FCS.GlobalComponents;

namespace FCS.Emulator
{
    /// <summary>
    /// This class is an emulation of the cRIO
    /// </summary>
    public class ERIO
    {
        // The current state of the fountain, by C# rules everything will be zero/false/off
        FountainState _state = new FountainState();
        // The UDPClient that will be responsible for all communication with the server
        UdpClient _udpClient;
        // Remote host to send data to (should be the Enlight Server)
        IPEndPoint _remoteHost;

        /// <summary>
        /// Init the eRIO by giving it an address to send messages back to along with port bindings for the UDP client
        /// </summary>
        /// <param name="addr">Address to send response packets to</param>
        /// <param name="localPort">The local port to listen on</param>
        /// <param name="remotePort">The port to send to on the remote host</param>
        public ERIO(IPAddress addr, int localPort, int remotePort)
        {
            _udpClient = new UdpClient(localPort);
            _remoteHost = new IPEndPoint(addr, remotePort);
        }

        ~ERIO()
        {
            this.Disconnect();
        }

        /// <summary>
        /// Close the UDP connection.
        /// </summary>
        public void Disconnect()
        {
            _udpClient.Close();
        }

        /// <summary>
        /// The UDP client listens asynchronously. To allow the calling program to know when the packet has arrived, the caller will pass in a function that
        /// will be notified when the packet is returned
        /// </summary>
        /// <example>Be sure to use BeginReceive and pass the AsyncCallback method into the clientS
        /// <code>
        /// UdpClient udp;
        /// IPEndPoint e = new IPEndPoint(IPAddress.Any, listenPort);
        /// UdpClient u = new UdpClient(e);
        ///
        /// UdpState s = new UdpState();
        /// s.e = e;
        /// s.u = u;
        /// ... 
        /// upd.BeginReceive(method, s);
        /// </code>
        /// </example>
        /// <param name="method">Method to receive the packet</param>
        public IAsyncResult ListenAsync(AsyncCallback method)
        {
            UdpState state = new UdpState();
            state.e = _remoteHost;
            state.u = _udpClient;

            // Begin the asynchronous receive operation
            return _udpClient.BeginReceive(method, state);
        }

        /// <summary>
        /// Calls the EndRecieve method of the internal UDP client. Once a packet is received, its contents will be returned.
        /// </summary>
        /// <param name="iaSync">Must be the IASyncResult returned by ERIO.ListenAsync</param>
        /// <returns>A UDP packet in a byte[]</returns>
        public byte[] EndListenAsync(IAsyncResult iaSync)
        {
            return _udpClient.EndReceive(iaSync, ref _remoteHost);
        }

        /// <summary>
        /// This method takes in the received byte message and converts it into a FountainState
        /// </summary>
        /// <param name="message">Byte message received from server</param>
        /// <returns>A FountainState that represents the next requested valve state of the fountain. The internal state of the
        /// fountain is not modified; SetState() should be used afterwards to accomplish this.</returns>
        public FountainState ParseToFountainState(byte[] message)
        {
            // A FountainState to build and then return at the end, copy the current state since a new packet may not
            // have all of the state info.
            // We do not want to modify the actual state. That will be up to the calling program to do, using SetState.
            FountainState state = _state.DeepCopy();
            // Array with parts of the packet not yet parsed
            byte[] remainingPacket = message;
            int indexCounter;

            if (message == null)
            {
                throw new ArgumentNullException();
            }

            // Loop to divide the packet into parts based on the header byte
            do
            {
                // This should be reset everytime for predictable and error-free results (sounds like a radio ad)
                indexCounter = 0;
                if (remainingPacket[0] == 0)
                {
                    // This is a ping packet, requesting the cRIO send back a status packet and not modify the state
                    // TODO: if there are more bytes in the packet after the 0x0, are they ignored?

                    // Copy everything after these bytes back into the array
                    Array.Copy(remainingPacket, PacketSizeConstants.Ping,
                        remainingPacket, 0, remainingPacket.Length - PacketSizeConstants.Ping);
                }
                if (remainingPacket[0] == 1 && remainingPacket.Length >= PacketSizeConstants.SouthEndValveState)
                {
                    // Next part of the packet should be describing the south valve states
                    // We can do the horizonal and vertical valve states simultaneously, because their fields are structured
                    // identically
                    for (int i = 3; i >= 0; i--, indexCounter++)
                    {
                        state._horizontalValveStates[indexCounter] = ((remainingPacket[1] & (1 << i)) != 0 ? true : false);
                        state._verticalValveStates[indexCounter] = ((remainingPacket[3] & (1 << i)) != 0 ? true : false);
                    }
                    for (int i = 7; i >= 0; i--, indexCounter++)
                    {
                        state._horizontalValveStates[indexCounter] = ((remainingPacket[2] & (1 << i)) != 0 ? true : false);
                        state._verticalValveStates[indexCounter] = ((remainingPacket[4] & (1 << i)) != 0 ? true : false);
                    }

                    // Reverse the values in the states; in the packet they are in the opposite order that we want them to be
                    state._horizontalValveStates.Reverse();
                    state._verticalValveStates.Reverse();

                    // Copy everything after these bytes back into the array
                    byte[] temp = new byte[remainingPacket.Length - PacketSizeConstants.SouthEndValveState];
                    Array.Copy(remainingPacket, PacketSizeConstants.SouthEndValveState,
                        temp, 0, remainingPacket.Length - PacketSizeConstants.SouthEndValveState);
                    remainingPacket = temp;
                }
                else if (remainingPacket[0] == 2 && remainingPacket.Length >= PacketSizeConstants.NorthEndValveState)
                {
                    // Next part of packet should be describing north end valve states
                    state._northValveStates[0] = ((remainingPacket[1] & 1) != 0 ? true : false);
                    indexCounter = 1;

                    // Making three loops seems wasteful, but the third is a bit different, so I think this way is cleanest
                    for (int i = 7; i >= 0; i--, indexCounter++)
                    {
                        state._northValveStates[indexCounter] = ((remainingPacket[2] & (1 << i)) != 0 ? true : false);
                    }
                    for (int i = 7; i >= 0; i--, indexCounter++)
                    {
                        state._northValveStates[indexCounter] = ((remainingPacket[3] & (1 << i)) != 0 ? true : false);
                    }
                    // There are only seven bits used in the final byte
                    for (int i = 7; i >= 1; i--, indexCounter++)
                    {
                        state._northValveStates[indexCounter] = ((remainingPacket[4] & (1 << i)) != 0 ? true : false);
                    }

                    // Save the final valve
                    state._northValveState = ((remainingPacket[4] & 1) != 0 ? true : false);

                    // Reverse the values in the states; in the packet they are in the opposite order that we want them to be
                    state._northValveStates.Reverse();

                    // Copy everything after these bytes back into the array
                    byte[] temp = new byte[remainingPacket.Length - PacketSizeConstants.NorthEndValveState];
                    Array.Copy(remainingPacket, PacketSizeConstants.NorthEndValveState,
                        temp, 0, remainingPacket.Length - PacketSizeConstants.NorthEndValveState);
                    remainingPacket = temp;
                }
                else if (remainingPacket[0] == 3 && remainingPacket.Length >= PacketSizeConstants.Weirs)
                {
                    // Next part of packet should be describing the weir states
                    for (int i = 2; i >= 0; i--, indexCounter++)
                    {
                        state._northValveStates[indexCounter] = ((remainingPacket[2] & (1 << i)) != 0 ? true : false);
                    }

                    // Reverse the values in the states; in the packet they are in the opposite order that we want them to be
                    state._weirStates.Reverse();

                    // Copy everything after these bytes back into the array 
                    byte[] temp = new byte[remainingPacket.Length - PacketSizeConstants.Weirs];
                    Array.Copy(remainingPacket, PacketSizeConstants.Weirs, temp,
                        0, remainingPacket.Length - PacketSizeConstants.Weirs);
                    remainingPacket = temp;
                }
                else if (remainingPacket[0] == 6 && remainingPacket.Length >= PacketSizeConstants.Misters)
                {
                    // Next part of packet will be mister data
                    state._northMisterState = ((remainingPacket[1] & 2) != 0 ? true : false);
                    state._southMisterState = ((remainingPacket[1] & 1) != 0 ? true : false);

                    // Copy everything after these  bytes back into the array 
                    byte[] temp = new byte[remainingPacket.Length - PacketSizeConstants.Misters];
                    Array.Copy(remainingPacket, PacketSizeConstants.Misters, temp,
                        0, remainingPacket.Length - PacketSizeConstants.Misters);
                    remainingPacket = temp;
                }
                else if (remainingPacket[0] == 7 && remainingPacket.Length >= PacketSizeConstants.LightControl)
                {
                    // Next part of packet will be lights control data
                    // Note that the lights data works differently. It is not encoded into bits. Each byte represents one
                    // light, and if its value is greater than 128, the light is switched on.
                    for (int i = 1; i <= FountainConstants.LightCount; i++)
                    {
                        state._lightStates[i] = (remainingPacket[i] > 128 ? true : false);
                    }

                    // Copy everything after these bytes back into the array 
                    byte[] temp = new byte[remainingPacket.Length - PacketSizeConstants.LightControl];
                    Array.Copy(remainingPacket, PacketSizeConstants.LightControl,
                        temp, 0, remainingPacket.Length - PacketSizeConstants.LightControl);
                    remainingPacket = temp;
                }
                else
                {
                    // Remainder of packet is non-conformant, get angry
                    // Whatever has been successfully parsed will still be saved to the actual fountain state.
                    throw new ArgumentException("Part or all of the update packet sent was invalid.");
                }
            } while (remainingPacket.Length > 0);  //might have to change this to != null depending on implementation

            return state;
        }

        /// <summary>
        /// This will send the status code packet of the fountain back to the sender in a UDP packet
        /// </summary>
        public void SendStatus()
        {
            byte[] state = new byte[PacketSizeConstants.CRioStatus];
                
            // Add the data for the bollards
            for (int j = FountainConstants.BollardCount - 1; j >= 0; j--)
            {
                // Element 0 of _bollardStates corresponds to 5th least significant bit of state[0]
                // Element 1 of _bollardStates corresponds to 4th least significant bit of state[0]
                // ...
                state[0] += (byte)((_state._bollardStates[FountainConstants.BollardCount - 1 - j] ? 1 : 0) << j);
            }

            // Add the data for the pumps
            for (int j = FountainConstants.PumpCount - 1; j >= 0; j--)
            {
                // The pump data works the same as the bollard states, except with 4 instead of 5 elements
                state[1] += (byte)((_state._pumpStates[FountainConstants.PumpCount - 1 - j] ? 1 : 0) << j);
            }

            // Add the data for the other miscellaneous sensors
            for (int j = FountainConstants.ReportedEnvironmentalStates - 1; j >= 0; j--)
            {
                state[2] += (byte)((_state._reportedEnvironStates[j] ? 1 : 0) << j);
            }

            // Send this to the server
            _udpClient.Send(state, PacketSizeConstants.CRioStatus, _remoteHost);
        }

        /// <summary>
        /// Set the new state of the fountain. Note that this will override the last state of the fountain similar to how the
        /// entire cRIO is updated a new packet is sent in.
        /// </summary>
        /// <remarks>
        /// B1-B1 for bollards;
        /// P1-P4 for pumps;
        /// M for manhole closed;
        /// S for south end disable;
        /// N for north end disable
        /// </remarks>
        /// <param name="newState">The next state of the fountain</param>
        public void SetState(FountainState newState)
        {
            if (newState == null)
            {
                throw new ArgumentNullException();
            }
            _state = newState;
        }

        /// <summary>
        /// This get the current state of the emulator including the state of the valves. This is not a reference to the
        /// internal state object- just a copy with identical values.
        /// </summary>
        /// <returns>The complete state of the fountain</returns>
        public FountainState GetStateCopy()
        {
              return _state.DeepCopy();
        }

        /// <summary>
        /// This allows a the simulator program to set the states of the pumps, bollards, and other variables normally
        /// read by the cRIO so they can be sent in eRIO packets back to the server.
        /// </summary>
        /// <param name="pumpStates">Four bools to set the states of the pumps, with index 0 representing P1</param>
        /// <param name="bollardStates">Five bools to set the states of the bollards, with index 0 representing B1</param>
        /// <param name="reportedEnvironStates">Four bools to set the states of the manhole cover, sidewalk sensor, south end-
        /// and north-end HW disables, in that order</param>
        /// <returns>Returns the new FountainState object that results from the change in HW states..</returns>
        public FountainState SetFountainHardware(bool[] pumpStates, bool[] bollardStates,
            bool[] reportedEnvironStates, bool windSensorState)
        {
            // Unlike ParseToFountainState, we do want to set the fountain hardware right away with this. One motivation is
            // that not as much error checking and parsing is done in this method as in chewing through UDP packets
            FountainState state = _state;

            // Make sure arguments are valid first
            if(pumpStates == null || bollardStates == null || reportedEnvironStates == null)
            {
                throw new ArgumentNullException();
            }
            if (pumpStates.Length != FountainConstants.PumpCount || bollardStates.Length != FountainConstants.BollardCount ||
                reportedEnvironStates.Length != FountainConstants.ReportedEnvironmentalStates)
            {
                throw new ArgumentException("An argument was an array of incorrect size.");
            }

            // Assign the pump states
            for (int i = 0; i < FountainConstants.PumpCount; i++)
            {
                state._pumpStates[i] = pumpStates[i];
            }
            // Assign the bollard states
            for (int i = 0; i < FountainConstants.BollardCount; i++)
            {
                state._bollardStates[i] = bollardStates[i];
            }
            // Assign the environmental states
            for (int i = 0; i < FountainConstants.ReportedEnvironmentalStates; i++)
            {
                state._reportedEnvironStates[i] = reportedEnvironStates[i];
            }
            state._windSensorState = windSensorState;

            return state;
        }
    }
}
